package cn.openea.perpartner.system.service.impl;


import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.BCrypt;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpUtil;
import cn.openea.perpartner.common.core.constant.UserConstants;
import cn.openea.perpartner.common.core.web.domain.AjaxResult;
import cn.openea.perpartner.common.core.web.domain.AjaxResult.Type;
import cn.openea.perpartner.common.redis.service.RedisCache;
import cn.openea.perpartner.system.entity.SysUser;
import cn.openea.perpartner.system.entity.SysUserUnion;
import cn.openea.perpartner.system.entity.dto.UserCacheDto;
import cn.openea.perpartner.system.entity.dto.UserInfoDto;
import cn.openea.perpartner.system.enums.AppletsEnums;
import cn.openea.perpartner.system.mapper.SysUserMapper;
import cn.openea.perpartner.system.service.ISysUserService;
import cn.openea.perpartner.system.service.ISysUserUnionService;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.yulichang.base.MPJBaseServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import java.util.*;
import java.util.stream.Collectors;


/**
 * @author Administrator
 */
@Slf4j
@Service
public class SysUserServiceImpl extends MPJBaseServiceImpl<SysUserMapper, SysUser> implements ISysUserService {
    @Autowired
    private RedisCache redisCache;
    @Autowired
    private ISysUserUnionService unionService;

    /**
     * 项目启动时，初始化参数到缓存
     */
    @PostConstruct
    public void init() {
        initUserCacheList();
    }

    @Override
    public AjaxResult checkUser(String userid, String pwd) {
        if (StrUtil.isEmpty(userid)) {
            return new AjaxResult(Type.ERROR, "Please input username.");
        }
        if (StrUtil.isEmpty(pwd)) {
            return new AjaxResult(Type.ERROR, "Please input password.");
        }
        SysUser user = findUser(userid, pwd);
        if (BeanUtil.isNotEmpty(user)) {
            return new AjaxResult(Type.SUCCESS, "login success.", user);
        }
        return new AjaxResult(Type.ERROR, "username or password is invalid.", user);
    }

    @Override
    public String checkUserUnique(String userid) {
        LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
        wrapper.and(query -> query.eq(SysUser::getUserid, userid)
                .or().eq(SysUser::getUnionid, userid));
        return count(wrapper) > 0 ? UserConstants.NOT_UNIQUE : UserConstants.UNIQUE;
    }

    @Override
    public SysUser findUser(String userid) {
        LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
        wrapper.and(query -> query.eq(SysUser::getUserid, userid)
                .or().eq(SysUser::getUnionid, userid)
                .or().eq(SysUser::getMyopenid, userid));
        SysUser user = getOne(wrapper, false);
        return BeanUtil.isNotEmpty(user) ? user : new SysUser();
    }

    @Override
    public List<UserInfoDto> selectAsyncList(String startTime, String endTime, String appid) {
        return baseMapper.selectJoinList(UserInfoDto.class, new MPJLambdaWrapper<SysUser>()
                .selectAll(SysUser.class)
                .select(SysUserUnion::getOpenid)
                .leftJoin(SysUserUnion.class, SysUserUnion::getUnionid, SysUser::getUnionid)
                .eq(SysUser::getUserid, SysUserUnion::getUserid)
                .eq(SysUserUnion::getAppid, appid)
                .between(SysUser::getUpdatetime, startTime, endTime));
    }

    @Override
    public UserInfoDto selectByUserId(String userid, String appId) {
        UserInfoDto userInfo = baseMapper.selectJoinOne(UserInfoDto.class, new MPJLambdaWrapper<SysUser>()
                .selectAll(SysUser.class)
                .select(SysUserUnion::getOpenid)
                .leftJoin(SysUserUnion.class, SysUserUnion::getUnionid, SysUser::getUnionid)
                .eq(SysUser::getUserid, SysUserUnion::getUserid)
                .eq(SysUser::getUserid, userid)
                .eq(SysUserUnion::getAppid, AppletsEnums.getApplet(appId).getAppId()));
        return BeanUtil.isNotEmpty(userInfo) ? userInfo : new UserInfoDto();
    }

    @Override
    public UserCacheDto selectUserCacheByUserId(String userid) {
        String key = this.getCacheKey(userid);
        if (Boolean.TRUE.equals(redisCache.hasKey(key))) {
            return redisCache.getCacheObject(key);
        }
        UserCacheDto userCacheDto = baseMapper.selectJoinOne(UserCacheDto.class, new MPJLambdaWrapper<SysUser>()
                .select(SysUser::getUserid, SysUser::getUnionid, SysUser::getNickname)
                .select(SysUser::getPhonenumber, SysUser::getAvatar, SysUser::getIsSubscribed)
                .selectAs(SysUser::getMyopenid, UserCacheDto::getSubOpenId)
                .select(SysUserUnion::getOpenid)
                .leftJoin(SysUserUnion.class, SysUserUnion::getUnionid, SysUser::getUnionid)
                .eq(SysUser::getUserid, SysUserUnion::getUserid)
                .eq(SysUser::getUserid, userid)
                .eq(SysUserUnion::getAppid, AppletsEnums.MEILY.getAppId()));
        if (BeanUtil.isNotEmpty(userCacheDto)) {
            redisCache.setCacheObject(key, userCacheDto);
        }
        return userCacheDto;
    }

    @Override
    public List<UserCacheDto> selectUserCacheList(List<String> userIds) {
        List<UserCacheDto> userList = new ArrayList<>();
        userIds.forEach(userid -> {
            String key = getCacheKey(userid);
            if (Boolean.TRUE.equals(redisCache.hasKey(key))) {
                userList.add(redisCache.getCacheObject(key));
            }
            if (Boolean.FALSE.equals(redisCache.hasKey(key))) {
                UserCacheDto userCacheDto = baseMapper.selectJoinOne(UserCacheDto.class, new MPJLambdaWrapper<SysUser>()
                        .select(SysUser::getUserid, SysUser::getUnionid, SysUser::getNickname)
                        .select(SysUser::getPhonenumber, SysUser::getAvatar, SysUser::getIsSubscribed)
                        .selectAs(SysUser::getMyopenid, UserCacheDto::getSubOpenId)
                        .select(SysUserUnion::getOpenid)
                        .leftJoin(SysUserUnion.class, SysUserUnion::getUnionid, SysUser::getUnionid)
                        .eq(SysUser::getUserid, SysUserUnion::getUserid)
                        .eq(SysUser::getUserid, userid)
                        .eq(SysUserUnion::getAppid, AppletsEnums.MEILY.getAppId()));
                if (BeanUtil.isNotEmpty(userCacheDto)) {
                    redisCache.setCacheObject(key, userCacheDto);
                    userList.add(userCacheDto);
                }
            }
        });
        return userList;
    }

    @Override
    public boolean removeUserCache() {
        Set<String> keys = (Set<String>) redisCache.keys(this.getCacheKey("*"));
        return redisCache.deleteObject(keys) > 0;
    }

    @Override
    public boolean removeCacheByUserId(String userId) {
        return redisCache.deleteObject(this.getCacheKey(userId));
    }

    @Override
    public boolean updateUserInfo(SysUser user) {
        LambdaUpdateWrapper<SysUser> wrapper = Wrappers.lambdaUpdate();
        wrapper.eq(SysUser::getUserid, user.getUserid());
        wrapper.eq(SysUser::getUnionid, user.getUnionid());
        UserCacheDto userCacheDto = new UserCacheDto();
        BeanUtil.copyProperties(user, userCacheDto, true);
        userCacheDto.setSubOpenId(user.getMyopenid());
        SysUserUnion union = unionService.getOne(Wrappers.<SysUserUnion>lambdaQuery()
                .eq(SysUserUnion::getUnionid, user.getUnionid())
                .eq(SysUserUnion::getUserid, user.getUserid())
                .eq(SysUserUnion::getAppid, AppletsEnums.getApplet(user.getAppletId()).getAppId()));
        userCacheDto.setOpenId(BeanUtil.isNotEmpty(union) ? union.getOpenid() : "");
        String key = this.getCacheKey(user.getUserid());
        redisCache.setCacheObject(key, userCacheDto);
        return update(user, wrapper);
    }

    @Override
    public void updateUserCache(SysUser user) {
        UserCacheDto userCacheDto = new UserCacheDto();
        BeanUtil.copyProperties(user, userCacheDto, true);
        userCacheDto.setSubOpenId(user.getMyopenid());
        LambdaQueryWrapper<SysUserUnion> wrapper = Wrappers.lambdaQuery();
        if (StrUtil.isNotBlank(user.getUnionid())) {
            wrapper.eq(SysUserUnion::getUnionid, user.getUnionid());
        }
        wrapper.eq(SysUserUnion::getUserid, user.getUserid());
        wrapper.eq(SysUserUnion::getAppid, AppletsEnums.getApplet(user.getAppletId()).getAppId());
        SysUserUnion union = unionService.getOne(wrapper, false);
        userCacheDto.setOpenId(BeanUtil.isNotEmpty(union) ? union.getOpenid() : "");
        String key = this.getCacheKey(user.getUserid());
        redisCache.setCacheObject(key, userCacheDto);
    }

    @Override
    public List<UserCacheDto> initUserCacheList() {
        List<UserCacheDto> userCacheList = baseMapper.selectJoinList(UserCacheDto.class, new MPJLambdaWrapper<SysUser>()
                .select(SysUser::getUserid, SysUser::getUnionid, SysUser::getNickname)
                .select(SysUser::getPhonenumber, SysUser::getAvatar, SysUser::getIsSubscribed)
                .selectAs(SysUser::getMyopenid, UserCacheDto::getSubOpenId)
                .select(SysUserUnion::getOpenid)
                .leftJoin(SysUserUnion.class, SysUserUnion::getUnionid, SysUser::getUnionid)
                .eq(SysUser::getUserid, SysUserUnion::getUserid)
                .eq(SysUserUnion::getAppid, AppletsEnums.MEILY.getAppId()));
        userCacheList.forEach(userCacheDto -> {
            String key = getCacheKey(userCacheDto.getUserId());
            redisCache.setCacheObject(key, userCacheDto);
        });
        return userCacheList;
    }

    @Override
    public boolean resetPwd(String userid, String password) {
        LambdaUpdateWrapper<SysUser> wrapper = Wrappers.lambdaUpdate();
        wrapper.set(SysUser::getPassword, BCrypt.hashpw(password, BCrypt.gensalt()));
        wrapper.eq(SysUser::getUserid, userid);
        return update(wrapper);
    }

    @Override
    public boolean removeBatchByUserIds(List<String> idList) {
        LambdaQueryWrapper<SysUser> wrapper = Wrappers.<SysUser>lambdaQuery()
                .in(SysUser::getUserid, idList);
        return baseMapper.delete(wrapper) > 0;
    }



    /**
     * 校验用户密码是否正确
     *
     * @param userid 账号
     * @param pwd    密码
     * @return true or false
     */
    private SysUser findUser(String userid, String pwd) {
        LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
        wrapper.and(query -> query.eq(SysUser::getUserid, userid)
                .or().eq(SysUser::getUnionid, userid)
                .or().eq(SysUser::getMyopenid, userid)
                .or().eq(SysUser::getPhonenumber, userid)
                .or().eq(SysUser::getEmail, userid));
        SysUser user = baseMapper.selectOne(wrapper);
        if (BeanUtil.isNotEmpty(user)) {
            return BCrypt.checkpw(pwd, user.getPassword()) ? user : null;
        }
        return null;
    }


    /**
     * 设置cache key
     *
     * @param configKey 参数键
     * @return 缓存键key
     */
    private String getCacheKey(String configKey) {
        return "sys_user:" + configKey;
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean improveSubOpenid() {
        //1.查询所有未关注的用户
        LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SysUser::getIsSubscribed, 0);
        wrapper.apply(" cast(userid as signed )>0");
        List<SysUser> userList = list(wrapper);
        //2.从公众号拉取所有关注用户
        String accessToken = getAccessToken();
        Map<String, String>  userMap= getAllUserInfo(accessToken,"");
        for (SysUser user : userList) {
            String openid = userMap.get(user.getUnionid());
            if (StrUtil.isNotBlank(openid)) {
                user.setMyopenid(openid);
                user.setIsSubscribed("1");
            }
        }
        userList = userList.stream().filter(user -> user.getIsSubscribed().equals("1")).collect(Collectors.toList());
        //3.更新用户信息
        LambdaQueryWrapper<SysUserUnion> unionWrapper = new LambdaQueryWrapper<>();
        unionWrapper.in(SysUserUnion::getUnionid, userList.stream().map(SysUser::getUnionid).collect(Collectors.toList()));
        unionWrapper.eq(SysUserUnion::getAppid, AppletsEnums.MEILY.getAppId());
        List<SysUserUnion> unionList = unionService.list(unionWrapper);
        for (SysUserUnion union : unionList) {
            String openid = userMap.get(union.getUnionid());
            if (StrUtil.isNotBlank(openid)){
                union.setMyopenid(openid);
            }
        }
        unionService.saveOrUpdateBatch(unionList);
        saveOrUpdateBatch(userList);
        return true;
    }
    /**
     * 获取微信access_token
     **/
    private String getAccessToken() {
        String appid = "wxd78484319e4979b5";
        String secret = "0801b85df7a4c708f481dc3f456fec12";
        String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appid + "&secret=" + secret;
        String result = HttpUtil.get(url);
        if (StrUtil.isNotBlank(result)) {
            JSONObject wxObject = JSONObject.parseObject(result);
            return wxObject.getString("access_token");
        }
        return "";
    }

    private Map<String, String> getAllUserInfo(String access_token, String next_openid) {
        String url = "https://api.weixin.qq.com/cgi-bin/user/get?access_token=" + access_token + "&next_openid=" + next_openid;
        String result = HttpUtil.get(url);
        JSONObject wxObject = JSONObject.parseObject(result);
        JSONObject object = wxObject.getJSONObject("data");
        JSONArray array = object.getJSONArray("openid");
        List<String> openidList = array.toJavaList(String.class);
        Map<String, String> userMap = new HashMap<>();
        int batchSize = 100;
        int totalSize = array.size();
        for (int i = 0; i < totalSize; i += batchSize) {
            int endIndex = Math.min(i + batchSize, totalSize);
            List<String> subList = openidList.subList(i, endIndex);
            List<JSONObject> paramList = new ArrayList<>();
            for (String openid : subList) {
                JSONObject param = new JSONObject();
                param.put("openid", openid);
                param.put("lang", "zh_CN");
                paramList.add(param);
            }
            JSONObject params = new JSONObject();
            params.put("user_list", paramList);
            userMap.putAll(getBatchUserInfo(access_token, params.toJSONString()));
        }
        return userMap;
    }

    private Map<String, String> getBatchUserInfo(String access_token, String params) {
        Map<String, String> map = new HashMap<>();
        String url = "https://api.weixin.qq.com/cgi-bin/user/info/batchget?access_token=" + access_token;
        String result = HttpRequest.post(url).body(params).execute().body();
        JSONObject wxObject = JSONObject.parseObject(result);
        JSONArray array = wxObject.getJSONArray("user_info_list");
        for (Object o : array) {
            JSONObject object = (JSONObject) o;
            map.put(object.getString("unionid"), object.getString("openid"));
        }
        return map;
    }

}
